package references

import (
	"JVM/Jvmgo/GoToJVM/instructions/base"
	"JVM/Jvmgo/GoToJVM/runtimeData"
	"JVM/Jvmgo/GoToJVM/runtimeData/heap"
)

// 用于调用接口方法，它会在运行时搜索一个实现了这个接口方法的对象，找出合适方法进行调用
/*
	JVM规范：
		1. Its first operand, CP, refers to a constant pool entry
			denoting an interface method named MethodName
			with descriptor Descriptor that is a member of an interface MethodIntfName.
		2. MethodName is not <init>.
		3. MethodName is not <clinit>.
		4. Its second operand, Count, is a valid count operand (see below).
		5. One can validly replace types matching the type MethodIntfName
			and the argument types given in Descriptor on the incoming operand stack
			with the return type given in Descriptor, yielding the outgoing type state.

	注意：
		1. 该指令操作码后面跟着4字节而非2字节，前两个字节含义与其他相同，为uint16运行时常量池索引
		2. 第三字节值时给方法传递参数需要的slot数，可以根据方法描述符计算出来，不需要读取了
		3. 第四个字节值必须为0，时版主Java虚拟机可以向后兼容
*/

type INVOKE_INTERFACE struct {
	index uint
	//count uint8
	//zero  uint8
}

func (self *INVOKE_INTERFACE) FetchOperands(reader *base.BytecodeReader) {
	self.index = uint(reader.ReadUint16())
	reader.ReadUint8() // count值
	reader.ReadUint8() // 0
}

func (self *INVOKE_INTERFACE) Execute(frame *runtimeData.Frame) {
	cp := frame.Method().Class().ConstantPool()
	methodRef := cp.GetConstant(self.index).(*heap.InterfaceMethodRef)
	resolvedMethod := methodRef.ResolvedInterfaceMethod()
	if resolvedMethod.IsStatic() || resolvedMethod.IsPrivate() {
		panic("java.lang.IncompatibleClassChangeError")
	}

	// 这里resolvedMethod.ArgSlotCount() - 1 的原因与在invokespecial中一样，都是需要从操作数栈中获取this引用
	ref := frame.OperandStack().GetRefFromTop(resolvedMethod.ArgSlotCount() - 1)
	if ref == nil {
		panic("java.lang.NullPointerException") // todo
	}
	ref.AddRef()
	// 当前类是否实现了 methodRef.ResolvedClass() 接口
	if !ref.Class().IsImplements(methodRef.ResolvedClass()) {
		panic("java.lang.IncompatibleClassChangeError")
	}

	methodToBeInvoked := heap.LookupMethodInClass(ref.Class(),
		methodRef.Name(), methodRef.Descriptor())
	if methodToBeInvoked == nil || methodToBeInvoked.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}
	// 接口方法必须为  abstract public
	if !methodToBeInvoked.IsPublic() {
		panic("java.lang.IllegalAccessError")
	}

	base.InvokeMethod(frame, methodToBeInvoked)
}
